#include <iostream>
#include <mpi.h>
#include <cstdio>
#include <unistd.h>

#include "CribleMPI.hpp"
#include "Paire.hpp"
#include "Factor.hpp"
#include "Erathostene.hpp"
#include "Polynome.hpp"
#include "Paire.hpp"

using namespace std;


const int TAILLE_TAB_B = 100000;


void creerB(int * inTabB, int inTailleB, int inNbProc)
{
   int j;
   int b;
   int i =0 ;
   int indice = 0;
   
   int taille = inTailleB/inNbProc;                //Taille que fait chaque vecteur
   
   for( j = 1 ; j <= inNbProc ; j++)
   {
      b = j;
      for( i = 0  ; i < taille ; i++)
      {
         inTabB[indice] = b;
         b += inNbProc;
         indice++;
      }
   }
}


// Modifier taille listeCouple

// smooth ne sert à rien car p est premier...

Factor * CribleMPI::sieving(mpz_class inLowBound, mpz_class inUpBound, int inNbelementRationnal, Erathostene & erathostene, Polynome & poly, int inNbB) const
{
   int nbMaxCouple;
   int rank;
   Factor * resultat = NULL;
   
   
   MPI_Comm_rank(MPI_COMM_WORLD, &rank);
   
   //Calcul du nombre d'element des bases + 1
   
   
   nbMaxCouple = _algebraicFactorBase->getPaire() 
         + _rationnalFactorBase->getPaire()
         + inNbelementRationnal
         + 1;

   
   
   
   
   if (!rank)
   { resultat = Master(nbMaxCouple, inNbB); }
   else
   { Slave(inLowBound, inUpBound, inNbelementRationnal, erathostene, poly, nbMaxCouple); }
   
   return resultat;
}


//Il faut pack la taille de chaque nombre pour les 2 chaines


Factor * CribleMPI::Master(int inNbMaxCouple, int inNbB) const
{
   int nbProc, resteModu, nbCouple = 0, nbCoupleRecu = 0;
   int * tabB;
   int nbBBroadcast [1];
   Factor * listeCouple = NULL;
   
   bool * tabCheckB = new bool[inNbB];
   //On initialise a 0 tout les elements
   memset(tabCheckB, false, inNbB * sizeof(bool));
   
   MPI_Comm_size(MPI_COMM_WORLD, &nbProc);
   
   
   nbProc--; //Car le master est 1 processus qui n'intervient pas dans le calcul
   printf("Le sieving se deroulera sur %d processeurs\n", nbProc);
   
   //Pour une repartition equitable du tableau de b sur cahrque Proc
   resteModu = inNbB % nbProc;
   
   nbBBroadcast[0] = inNbB - resteModu;
   
   //On aura donc bien le meme nombre de b que de processeurs
   //int * tabB = new int[60];
   
   
   int nbB = nbBBroadcast[0] / nbProc;
   
   
   //On broadcast aux fils le nbr d'element cad inNbB + resteModu
   MPI_Bcast(&nbB, 1, MPI_INT, 0, MPI_COMM_WORLD);
   
   //On creer nbB case en plus car MPI_Scatter s'envois les donnees a lui meme
   tabB = new int[inNbB + nbB];

   //On se decale des premieres cases qui ne serviront pas pour remplir le tableau
   int * ptmp = tabB + nbB;
   
   //Tableau de B construit
   creerB(ptmp, inNbB, nbProc);
   
   //On envoit aux fils
   MPI_Scatter(tabB, nbB, MPI_INT, MPI_IN_PLACE, nbB, MPI_INT, 0, MPI_COMM_WORLD);	
   
   delete [] tabB;
   
   char * receptionLCouple = new char[tailleMax];
   
   Factor factorTmp(500);
   mpz_class valeurBCouple;      //Permet de savoir quel a été traité
   unsigned long int b;
   bool bContinue = true;
   int cptCouple;
   
   Factor ** tabFacteur = new Factor * [inNbB];
   for(int i = 0 ; i < inNbB ; i++)
   {
      tabFacteur[i] = NULL;
   }

//   cout << "On lance la booboocle \n";
   while(bContinue)
   {
      MPI_Recv(receptionLCouple, tailleMax, MPI_CHAR,
               MPI_ANY_SOURCE , MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
      
      if(receptionLCouple[0] == 'V')
      {
         //On a aucune paire
         sscanf(&receptionLCouple[1], "%ld", &b);
         tabCheckB[b - 1] = true;
      }
      else
      {
         factorTmp.purgeBase();
                  
         //On creer la paire a partie de la chaine
         factorTmp.converFactor(receptionLCouple);
                  
         valeurBCouple = factorTmp.getPaire(0).getPaire(1);
         
         b = mpz_get_ui(valeurBCouple.get_mpz_t());
         
         
         
         tabCheckB[b - 1] = true;
         
         tabFacteur[b-1] = new Factor(factorTmp);

         nbCouple += factorTmp.getTaille();
      }
      cout << "On a finit b = " << b << " => nombre de couples trouves : " << nbCouple << endl;

      //Test d'arret
      if(nbCouple > inNbMaxCouple)
      {
         //On a theoriquement assez de couple => on verifie de les avoirs dans l'ordre
         cptCouple = 0;
         nbCoupleRecu = 0;
         while(tabCheckB[cptCouple] && cptCouple < inNbB && nbCoupleRecu < inNbMaxCouple)
         {
            // S'il y a des paires pour cet indice
            if(tabFacteur[cptCouple])
            {
               nbCoupleRecu += tabFacteur[cptCouple]->getTaille();
            }
            
            cptCouple++;
         }
         if(nbCoupleRecu >= inNbMaxCouple)
         {
            //On a nos couple

            bContinue = false;
            listeCouple = new Factor(nbCouple);
            
            //On recopie la liste
            int indiceGlobal = 0;
            int indCoupleRes = 0;
            int j = 0;
            
            while(indiceGlobal < cptCouple)
            {
               if(tabFacteur[indiceGlobal])
               {
                  for(j = 0 ; j < tabFacteur[indiceGlobal]->getTaille() ; j++)
                  {
                     Paire tmpPaire = tabFacteur[indiceGlobal]->getPaire(j);
                     listeCouple->add(tmpPaire);
                     indCoupleRes++;
                  }         
               }

               ++indiceGlobal;
            }
         }
      }
   }
   for(int i = 0 ; i <  inNbB  ; i++)
   {
      if(tabFacteur[i])
      {
         delete tabFacteur[i];
      }
   }
   
   delete [] tabFacteur;
   
   delete [] tabCheckB;
   
   delete [] receptionLCouple;
   
   return listeCouple;
}




void CribleMPI::Slave(mpz_class inLowBound, mpz_class inUpBound, int inNbelementRationnal, Erathostene & erathostene, Polynome & poly, int inNbMaxCouple) const
{
   

   
   int nbCouple = 0;

   mpz_class smooth = 0;
   mpz_class pgcd = 0;
   mpz_class indiceJ = 0;
   mpz_class b = 1;
   mpz_class cptK;
   mpz_class indiceMax = 2 * inUpBound + 1;
   mpz_class valeur;  //Utilie dans le calcul du for
   char * chaineRes = NULL;
   
   Paire paireCourante;
   
   //On prend une marge de 50 couples
      
   Factor listeCouple(inNbMaxCouple + 50);
   Paire tmp;
   
   mpz_class * tab1 = new mpz_class [mpz_get_ui(indiceMax.get_mpz_t())];
   mpz_class * tab2 = new mpz_class [mpz_get_ui(indiceMax.get_mpz_t())];
   
   mpz_class * courant;
   mpz_class * courant2;
   
   mpz_class * fin2;
   mpz_class * fin;
   
   fin = &tab1[mpz_get_ui(indiceMax.get_mpz_t()) - 1];
   

   //Bloc qui recupere les données
   int nbB;

   //On recupere le nombre de B
   MPI_Bcast(&nbB, 1, MPI_INT, 0, MPI_COMM_WORLD);

   //tabB = new int[10];
   
   
   //On recupere le tableau
      
   int * tabB = new int[nbB];
   
   MPI_Scatter(NULL, 0, MPI_INT, tabB, nbB, MPI_INT, 0, MPI_COMM_WORLD);
   
   for(int indB = 0 ; indB < nbB ; indB++)
   {
      //Pn prend le bon B
      b = tabB[indB];
      listeCouple.purgeBase();
      // set a[i] = i + bm for i ∈ [−C; C]
      valeur = inLowBound;
      
      for(courant = tab1 ; courant <= fin ; courant++)
      {
         *courant = valeur + b * _m;
         if(*courant < 0) *courant = (-1) * (*courant);
         valeur += 1;
      }
      
      for(int i = 0 ; i < _rationnalFactorBase->getTaille() ; i++)
      {
         //On recupere la paire courante
         // Sous la forme (p,r)  
         paireCourante = _rationnalFactorBase->getPaire(i);
         
         /*
         Divide out largest power of p from a[j] where
         j = −bm + kp for k ∈ Z so that −C ≤ j ≤ C.
         */
         mpz_class p = paireCourante.getPaire(0);
         
         smooth = erathostene.higherFactor(p);
         
         
         // Recherche du premier indice
         cptK = (inLowBound + b * _m);
         cptK /= p;
         
         
         // Calcul du premier indice
         indiceJ = (-1) * b * _m + cptK * p;
         if(indiceJ < inLowBound)
            indiceJ += p;
         
         cptK = inLowBound;
         courant = tab1;
         while(cptK != indiceJ)
         {
            cptK++;
            courant++;
         }
         
         
         while(indiceJ <= inUpBound)
         {
            // Operation sur a
            if(*courant != 0)
            {
               do
               {
                  *courant /= smooth;
               }
               while(mpz_divisible_p((*courant).get_mpz_t(), smooth.get_mpz_t()));
            }
            
            // Indice suivant
            indiceJ += p;
            while(cptK != indiceJ)
            {
               cptK++;
               courant++;
            }
         }
         
      }
      
      // set e[i] = bdeg(f ) f (i/b) for i ∈ [−C; C]
      valeur = inLowBound ;
      fin2 = &tab2[mpz_get_ui(indiceMax.get_mpz_t()) - 1];
      
      for(courant2 = tab2 ; courant2 <= fin2 ; courant2++)
      {
         *courant2 = poly.bpowdf(valeur, b);
         if(*courant2 < 0) *courant2 = (-1) * (*courant2);
         valeur += 1;
      }

      for(int i = 0 ; i < _algebraicFactorBase->getTaille() ; i++)
      {
         //On recupere la paire courante
         // Sous la forme (p,r) 
         /*Divide out largest power of p from e[j] where
         j = −br + kp for k ∈ Z so that −C ≤ j ≤ C. */
         paireCourante = _algebraicFactorBase->getPaire(i);
         
         mpz_class p = paireCourante.getPaire(0);
         mpz_class r = paireCourante.getPaire(1);
         
         smooth = erathostene.higherFactor(p);
         
         // Recherche du premier indice
         cptK = (inLowBound + b * r);
         cptK /= p;
         
         // Calcul du premier indice
         indiceJ = (-1) * b * r + cptK * p;
         if(indiceJ < inLowBound)
            indiceJ += p;
         
         // Recherche de l'indice dans le tableau
         cptK = inLowBound;
         courant2 = tab2;
         while(cptK != indiceJ)
         {
            cptK++;
            courant2++;
         }
         
         
         while(indiceJ <= inUpBound)
         {
            // Operation sur e
            if(*courant2 != 0 && *courant2 != 1)
            {
               while (mpz_divisible_p((*courant2).get_mpz_t(), smooth.get_mpz_t()))
               {
                  *courant2 /= smooth;
               }

            }
            

            indiceJ += p;
            while(cptK != indiceJ)
            {
               cptK++;
               courant2++;
            }
         }
      }

      
      
      /* for i ∈ [−C; C] 
      i. if a[i] = e[i] = 1 and gcd(i, b) = 1 add (i, b) to rels  */
      
      valeur = inLowBound ;
      courant2 = tab2;
      
      for(courant = tab1 ; courant <= fin ; courant++)
      {
         mpz_gcd(pgcd.get_mpz_t(), valeur.get_mpz_t(), b.get_mpz_t());
         if( *courant == *courant2 && *courant == 1 && pgcd == 1)
         {
            tmp.setPaire(valeur, b);
            listeCouple.add(tmp);
            
            
            ++nbCouple;
         }
         ++courant2;
         valeur += 1;
         
      }
      
      
      if(!(listeCouple.getPaire(0)).isNull())
      {
         //On a au moins un facteur
         //On convertit la chaine en chaine de caractere
         //On ne traite pas le cas ou il faut rajouter des couples.
                  
         
         chaineRes = listeCouple.converMPI();
         

         if(chaineRes[0])
         {
            //On a des paires => On les envois
            MPI_Send(chaineRes, strlen(chaineRes) + 1, MPI_CHAR, 0, 1, MPI_COMM_WORLD);
//             cout << "On a envoye resulat" << endl;
         }

      }
      else
      {
            //On en a pas => On envois une chaine speciale V + le nombre B
         char chaineNCouple[50];
         chaineNCouple[0] = 'V';
         chaineNCouple[1] = '\0';
         
         strcat(chaineNCouple, mpz_get_str(NULL, 10, b.get_mpz_t()));
            
         MPI_Send(chaineNCouple, strlen(chaineNCouple) + 1, MPI_CHAR, 0, 1, MPI_COMM_WORLD);
      }
   }
   
   
   delete [] tab1;
   delete [] tab2;
}

